/*
# _____     ___ ____     ___ ____
#  ____|   |    ____|   |        | |____|
# |     ___|   |____ ___|    ____| |    \    PS2DEV Open Source Project.
#-----------------------------------------------------------------------
# Licenced under Academic Free License version 2.0
# Review ps2sdk README & LICENSE files for further details.
*/

/**
 * @file
 * EE kernel update for thread functions
 */

#include "kernel.h"

struct request
{
    u8 mode;
    u8 data;
};

enum TOP_REQ {
    TOP_REQ_WAKEUP,
    TOP_REQ_ROTATE,
    TOP_REQ_SUSPEND
};

struct topArg
{
    int requestOut;
    int requestIn;
    struct request request[512];
};

extern int topId;
extern int topSema;
extern struct topArg topArg;

#ifdef F__thread_internals

extern void *_gp;

u8 stack[0x400] __attribute__((aligned(16)));

int topId            = 0;
int topSema          = 0;
struct topArg topArg = {0};

static void topThread(void *arg)
{
    (void)arg;

    while (1) {
        int index;

        WaitSema(topSema);
        index             = topArg.requestOut & 0x1FF;
        topArg.requestOut = index + 1;

        switch (topArg.request[index].mode) {
            case TOP_REQ_WAKEUP:
                WakeupThread(topArg.request[index].data);
                break;
            case TOP_REQ_ROTATE:
                RotateThreadReadyQueue(topArg.request[index].data);
                break;
            case TOP_REQ_SUSPEND:
                SuspendThread(topArg.request[index].data);
                break;
                /* default:
                    Kprintf("## internal error in libkernel!\n"); */
        }
    }
}

__attribute__((weak))
int InitThread(void)
{
    ee_sema_t sema;
    ee_thread_t thread;

    sema.max_count  = 255;
    sema.init_count = 0;
    sema.option     = (u32) "KernelTopThread";
    if ((topSema = CreateSema(&sema)) < 0)
        return -1;

    thread.func             = &topThread;
    thread.stack            = stack;
    thread.stack_size       = sizeof(stack);
    thread.gp_reg           = &_gp;
    thread.option           = (u32) "KernelTopThread";
    thread.initial_priority = 0;
    if ((topId = CreateThread(&thread)) < 0) {
        DeleteSema(topSema);
        return -1;
    }

    topArg.requestOut = 0;
    topArg.requestIn  = 0;
    StartThread(topId, &topArg);

    ChangeThreadPriority(GetThreadId(), 1);

    return topId;
}
#endif

#ifdef F_iWakeupThread
// The original iWakeupThread cannot wake up threads in THS_RUN state.
__attribute__((weak))
s32 iWakeupThread(s32 thread_id)
{
    if (_iGetThreadId() == thread_id) {
        if (thread_id < 256 && topId != 0) {
            int index;

            index                      = topArg.requestIn & 0x1FF;
            topArg.requestIn           = index + 1;
            topArg.request[index].mode = TOP_REQ_WAKEUP;
            topArg.request[index].data = thread_id;

            iSignalSema(topSema);
            return 0;
        } else
            return -1;
    } else {
        return _iWakeupThread(thread_id);
    }
}
#endif

#ifdef F_iRotateThreadReadyQueue
// The original iRotateThreadReadyQueue will not change the current thread ID.
__attribute__((weak))
s32 iRotateThreadReadyQueue(s32 priority)
{
    if (priority < 128 && topId != 0) {
        int index;

        index                      = topArg.requestIn & 0x1FF;
        topArg.requestIn           = index + 1;
        topArg.request[index].mode = TOP_REQ_ROTATE;
        topArg.request[index].data = priority;

        iSignalSema(topSema);
        return 0;
    } else
        return -1;
}
#endif

#ifdef F_iSuspendThread
// The original iSuspendThread allows a thread to suspend itself, but won't change the current thread ID.
__attribute__((weak))
s32 iSuspendThread(s32 thread_id)
{
    if (_iGetThreadId() == thread_id) {
        if (thread_id < 256 && topId != 0) {
            int index;

            index                      = topArg.requestIn & 0x1FF;
            topArg.requestIn           = index + 1;
            topArg.request[index].mode = TOP_REQ_SUSPEND;
            topArg.request[index].data = thread_id;

            iSignalSema(topSema);
            return 0;
        } else
            return -1;
    } else {
        return _iSuspendThread(thread_id);
    }
}
#endif
